package org.fujay.langchain4jserver.infrastructure.persistence.repository;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.fujay.langchain4jserver.domain.application.aggregate.AppAggregate;
import org.fujay.langchain4jserver.domain.application.entity.App;
import org.fujay.langchain4jserver.domain.application.entity.AppConfigHistory;
import org.fujay.langchain4jserver.domain.application.entity.AppUsageStats;
import org.fujay.langchain4jserver.domain.application.repository.AppRepository;
import org.fujay.langchain4jserver.domain.application.vo.AppId;
import org.fujay.langchain4jserver.domain.model.vo.ModelId;
import org.fujay.langchain4jserver.infrastructure.persistence.assembler.AppAssembler;
import org.fujay.langchain4jserver.infrastructure.persistence.mybatis.entity.AppConfigHistoryPo;
import org.fujay.langchain4jserver.infrastructure.persistence.mybatis.entity.AppPo;
import org.fujay.langchain4jserver.infrastructure.persistence.mybatis.entity.AppUsageStatsPo;
import org.fujay.langchain4jserver.infrastructure.persistence.mybatis.mapper.AppConfigHistoryMapper;
import org.fujay.langchain4jserver.infrastructure.persistence.mybatis.mapper.AppMapper;
import org.fujay.langchain4jserver.infrastructure.persistence.mybatis.mapper.AppUsageStatsMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 应用仓储实现
 */
@Repository
@RequiredArgsConstructor
public class AppRepositoryImpl implements AppRepository {

    private final AppMapper appMapper;
    private final AppConfigHistoryMapper appConfigHistoryMapper;
    private final AppUsageStatsMapper appUsageStatsMapper;

    @Override
    public Optional<AppAggregate> findById(AppId id) {
        Optional<AppPo> appPOOptional = Optional.ofNullable(appMapper.selectById(id.value()));
        if (appPOOptional.isEmpty()) {
            return Optional.empty();
        }
        App app = AppAssembler.toDomainEntity(appPOOptional.get());

        List<AppConfigHistoryPo> historyPOs = appConfigHistoryMapper.selectByAppId(id.value());

        List<AppConfigHistory> historyList =
                historyPOs.stream().map(AppAssembler::toHistoryEntity).collect(Collectors.toList());

        return Optional.of(AppAggregate.load(app, historyList));
    }

    @Override
    public List<AppAggregate> findAll() {
        List<AppPo> appPOs = appMapper.selectList(Wrappers.emptyWrapper());
        return appPOs.stream().map(AppAssembler::toAggregateShallow).collect(Collectors.toList());
    }

    @Override
    public Optional<AppAggregate> findAppShallow(AppId appId) {
        AppPo appPO = appMapper.selectById(appId.value());
        return Optional.of(AppAssembler.toAggregateShallow(appPO));
    }

    @Override
    @Transactional
    public void save(AppAggregate aggregate) {
        if (aggregate == null) {
            return;
        }
        App app = aggregate.getApp();
        AppPo appPO = AppAssembler.toPO(aggregate);

        boolean exists =
                appMapper.exists(new LambdaQueryWrapper<AppPo>().eq(AppPo::getId, app.getId().value()));

        if (exists) {
            appMapper.updateById(appPO);
        } else {
            if (appPO.getId() == null) {
                throw new IllegalStateException(
                        "App ID is null during save operation for a new aggregate.");
            }
            appMapper.insert(appPO);
        }
        /*todo 未处理List<AppConfigHistory> historyList*/
    }

    @Override
    @Transactional
    public void deleteById(AppId id) {
        appConfigHistoryMapper.deleteById(id.value());
        appUsageStatsMapper.delete(
                Wrappers.<AppUsageStatsPo>lambdaQuery().eq(AppUsageStatsPo::getAppId, id.value()));
        appMapper.deleteById(id.value());
    }

    public List<AppConfigHistoryPo> findConfigHistoryByAppId(AppId appId) {
        Assert.notNull(appId, "App ID cannot be null");
        throw new UnsupportedOperationException("Finding config history not fully implemented yet.");
    }

    public Optional<AppConfigHistoryPo> findConfigHistoryById(Long historyId) {
        Assert.notNull(historyId, "History ID cannot be null");
        throw new UnsupportedOperationException(
                "Finding specific config history not fully implemented yet.");
    }

    @Transactional
    public void saveHistory(AppConfigHistory history) {
        Assert.notNull(history, "AppConfigHistory cannot be null");
        AppConfigHistoryPo po = AppAssembler.toHistoryPO(history);
        if (history.isNew()) {
            appConfigHistoryMapper.insert(po);
            AppPo appPO = appMapper.selectById(history.getAppId().value());
            if (appPO != null) {
                appPO.setCurrentConfigHistoryId(po.getId());
                appMapper.updateById(appPO);
            }
        } else {
        }
    }

    @Override
    public List<AppConfigHistory> listHistoryByAppId(AppId appId) {
        Assert.notNull(appId, "AppId cannot be null");
        List<AppConfigHistoryPo> pos = appConfigHistoryMapper.selectByAppId(appId.value());
        return pos.stream().map(AppAssembler::toHistoryEntity).collect(Collectors.toList());
    }

    @Transactional
    @Override
    public void saveStats(AppUsageStats stats) {
        Assert.notNull(stats, "AppUsageStats cannot be null");
        AppUsageStatsPo po = AppAssembler.toStatsPO(stats);
        AppUsageStatsPo existing =
                appUsageStatsMapper.selectByAppIdAndDate(stats.getAppId().value(), stats.getDate());
        if (existing != null) {
            po.setId(existing.getId());
            appUsageStatsMapper.updateById(po);
        } else {
            appUsageStatsMapper.insert(po);
        }
    }

    @Override
    public List<AppUsageStats> queryStats(AppId appId, LocalDate startDate, LocalDate endDate) {
        Assert.notNull(appId, "AppId cannot be null");
        Assert.notNull(startDate, "Start date cannot be null");
        Assert.notNull(endDate, "End date cannot be null");
        List<AppUsageStatsPo> pos =
                appUsageStatsMapper.selectByAppIdAndDateRange(appId.value(), startDate, endDate);
        return pos.stream().map(AppAssembler::toStatsEntity).collect(Collectors.toList());
    }

    @Override
    public boolean existsById(AppId id) {
        return appMapper.exists(new LambdaQueryWrapper<AppPo>().eq(AppPo::getId, id.value()));
    }

    @Override
    public List<AppAggregate> findAllShallow() {
        return findAll();
    }

    @Override
    public long countByModelId(ModelId modelId) {
        if (modelId == null) {
            return 0L;
        }
        return appMapper.selectCount(
                new LambdaQueryWrapper<AppPo>().eq(AppPo::getModelId, modelId.value()));
    }
}
